package coalition.formation.coalitions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import coalition.formation.combinations.Combination;
import coalition.formation.combinations.SuperSet;
import coalition.formation.externality.Externality;
import coalition.formation.partitions.Partition;
import coalition.formation.utils.Util;




public class Core {
	
	Externality externality;
	int numElements;

	public Core(Externality externality){
		this.externality = externality;
		this.numElements = externality.getSize();
	}

	public List<int[]> getCore(){
		Partition testPartition = new Partition(numElements);
		ArrayList<int[]> partitions = testPartition.getPartitions();
		return getCore(partitions);
	}	
	
	public List<int[]> getCore(ArrayList<int[]> partitions){
		List<int[]> result = new ArrayList<int[]>();
		
		List<int[]> combinations = SuperSet.getSuperSetMinusEmptySet(numElements);
			
		for(int[] partition : partitions){
			System.out.println("starting partition:" + coalition.formation.utils.Util.printIntArray(partition));
			float[] thisExternalities = externality.getExternalities(partition);
			System.out.println("\texternalities:" + coalition.formation.utils.Util.printFloatArray(thisExternalities)+ "\n");
			boolean isCore = true;
			for(int[] combination: combinations){
					
				int[] coalition = getCoalitionFromCoalitionIndicatorArray(combination);
				System.out.println("\tcombination indicator:" + Util.printIntArray(combination));
				System.out.println("\tcoalition list:" + Util.printIntArray(coalition));
				
				int[] otherPartition = getPartitionWithDefectingAgents(partition, coalition);
				System.out.println("\tresulting partition:" + Util.printIntArray(otherPartition) );
				
				float[] otherExternalities = externality.getExternalities(otherPartition);
				
				boolean isGreaterThan = floatArrayGreaterThan(otherExternalities, thisExternalities, coalition);
				String cmpSymbol = isGreaterThan ? " > " : " !> ";
				System.out.println("\tresulting externalities:" + 
						Util.printFloatArray(otherExternalities, combination) + 
						cmpSymbol + 
						Util.printFloatArray(thisExternalities, combination)+
						"\n");
				
				if(isGreaterThan){
					isCore = false; 
					System.out.println("\tviolates core stability");
					break;
				}
			}		
			if(isCore)
				result.add(partition);					
		}
		
		return result;		
	}

	public static boolean floatArrayGreaterThan(float[] a, float[] b, int[] places) {

		if (a == null || b == null)
			throw new IllegalArgumentException("can't compare null arrays");
		
	    if (a.length != b.length)
	        throw new IllegalArgumentException("can't compare unequal arrays");
	    
	    if(a.length == 0){
	    	throw new IllegalArgumentException("trying to compare zero length arrays");
	    }
	    
	    if(places.length == 0){
	    	throw new IllegalArgumentException("trying to compare arrays at zero indices");
	    }	    

	    //for (int i = 0; i < a.length; i++) {
	    //    if (a[i] < b[i])
	    //        return false;
	    //}
	    
	    for (int i = 0; i < places.length; i++) {
	        int index = places[i];
	    	if (a[index] <= b[index])
	            return false;
	    }	    
	    
	    return true;
	}	
	
	public static boolean floatArrayWeaklyGreaterThan(float[] a, float[] b, int[] places) {

		if (a == null || b == null)
			throw new IllegalArgumentException("can't compare null arrays");
		
	    if (a.length != b.length)
	        throw new IllegalArgumentException("can't compare unequal arrays");
	    
	    if(a.length == 0){
	    	throw new IllegalArgumentException("trying to compare zero length arrays");
	    }
	    
	    if(places.length == 0){
	    	throw new IllegalArgumentException("trying to compare arrays at zero indices");
	    }	    

	    //for (int i = 0; i < a.length; i++) {
	    //    if (a[i] < b[i])
	    //        return false;
	    //}
	    
	    for (int i = 0; i < places.length; i++) {
	        int index = places[i];
	    	if (a[index] < b[index])
	            return false;
	    }	    
	    
	    return true;
	}		
	
	int[] getPartitionWithDefectingAgents(int[] partition, int[] coalition){
		int[] result = new int[partition.length];

		//calculate the maximum value seen before the leftmost element of the new coalition
		//by "new coalition" we mean the coalition that is the test for core-stability
		//its formation will imply the loss of members to other coalitions
		//and in some sense those coalitions may be considered to be new, but 
		//they are not what is being described here
		int maxValueBefore = -1;
		for(int i = 0; i < coalition[0]; i++)
			if(partition[i] > maxValueBefore){
				maxValueBefore++;
			}
		
		//the coalition will get a value 1 greater than any value to the left of
		//its leftmost element in the global agent ordering
		int defectingCoalitionValue = maxValueBefore + 1;
		
		//the new partition will have all the defecting agents belonging to the same
		//coalition and therefore with the same value
		for(int i = 0; i < coalition.length; i++){
			result[coalition[i]] = defectingCoalitionValue;
		}
		
		//all values to the left of the leftmost new coalition element
		//are unmodified
		for(int i = 0; i < coalition[0]; i++){
			result[i] = partition[i];
		}		
		
		//convert the list representation to an indicator representation
		int[] coalitionIdicatorArray = 
			getCoalitionIndicatorArray(coalition, partition.length);
		
		//this array maps old values to new ones
		//to find a new value, just take array[old value]
		//this works because we have a permutation mapping from 1->n to 1->n
		int[] oldValuesToNewValues = new int[partition.length];
		//all values to the left of leftmost agent in new coalition remain the same
		//note:  coalition will have at least one element since
		//we used the getSuperSetMinusEmptySet method above
		for(int i = 0; i < coalition[0]; i++)
			oldValuesToNewValues[i] = i;
		//set the remaining values to be -1 so we know they are unset
		for(int i = coalition[0]; i < oldValuesToNewValues.length; i++)
			oldValuesToNewValues[i] = -1;		
		
		int maxValueAfter = defectingCoalitionValue;
		for(int i = coalition[0]; i < partition.length; i++){
			
			//don't modify values of agents in new coalition; they have
			//been set appropriately above
			if(coalitionIdicatorArray[i] == 1) continue;
			
			//get the old coalition value for an agent
			int value = partition[i];
			
			if(oldValuesToNewValues[value] == -1){	//this old value has not been seen before
													//amongst elements not in the new coalition
				maxValueAfter++;	//set a new value for this old value
				//save it so all future instances of this old value will be mapped to this new value
				oldValuesToNewValues[value] = maxValueAfter;
			}
			//set the new value in the new partition array
			result[i] = oldValuesToNewValues[value];
		}
		
		return result;
	}
	
	//will tell us where in a partition values
	//appear for the first time; i.e. they do not appear
	//to their left
	int[] getNewValueIndexArray(int[] partition){
		int[] result = new int[partition.length];
		int maxNumSeen= -1;
		for(int i = 0; i < partition.length; i++){
			if(partition[i] > maxNumSeen) {
				result[i] = 1;
				maxNumSeen++;
			}
		}
		return result;
		
	}
	
	int countNewValuesInDefectingCoalition(int[] newValueIndexArray, int[] coalition){
		int newValues = 0;
		for(int i = 0; i < coalition.length; i++){
			if(newValueIndexArray[coalition[i]] == 1)
				newValues++;
		}
		return newValues;
	}
	
	public static int[] getCoalitionIndicatorArray(int[] coalition, int numElements){
		int[] coalitionIdicatorArray = new int[numElements];
		for(int i = 0; i < coalition.length; i++){
			coalitionIdicatorArray[coalition[i]] = 1;
		}
		return coalitionIdicatorArray;
	}	
	
	public static int[] getCoalitionComplementIndicatorArray(int[] coalition, int numElements){
		int[] coalitionIdicatorArray = new int[numElements];
		for(int i = 0; i < numElements; i++){
			coalitionIdicatorArray[i] = 1;
		}		
		for(int i = 0; i < coalition.length; i++){
			coalitionIdicatorArray[coalition[i]] = 0;
		}
		return coalitionIdicatorArray;
	}	
	
	public static int[] getCoalitionComplement(int[] coalition, int numElements){
		int[] coalitionComplement = new int[numElements - coalition.length];
		int[] coalitionComplementIdicatorArray = getCoalitionComplementIndicatorArray(coalition, numElements);
		int index = 0;
		for(int i = 0; i < numElements; i++){
			if(coalitionComplementIdicatorArray[i] == 1){
				coalitionComplement[index] = i;
				index++;
			}
		}		
		return coalitionComplement;
	}
	
	public static int[] getCoalitionFromCoalitionIndicatorArray(int[] coalition){
		List<Integer> coalitionList = new ArrayList<Integer>();
		for(int i = 0; i < coalition.length; i++){
			if(coalition[i] == 1)
				coalitionList.add(i);
		}
		int[] result = new int[coalitionList.size()];
		for(int i = 0; i < result.length; i++)
			result[i] = coalitionList.get(i);
		return result;
	}	
	
}
